#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "swscale.lib")

#define _CRT_SECURE_NO_WARNINGS
#define __STDC_CONSTANT_MACROS

#include <libavutil/parseutils.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>

#include <assert.h>
#include <errno.h>

static void fill_yuv_image(
	uint8_t *data[AV_NUM_DATA_POINTERS],
	int linesize[AV_NUM_DATA_POINTERS],
	int width, int height, int frame_index)
{
	int x, y;

	/* Y */
	for (y = 0; y < height; y++)
		for (x = 0; x < width; x++)
			data[0][y * linesize[0] + x] = x + y + frame_index * 3;

	/* Cb and Cr */
	for (y = 0; y < height / 2; y++) {
		for (x = 0; x < width / 2; x++) {
			data[1][y * linesize[1] + x] = 128 + y + frame_index * 2;
			data[2][y * linesize[2] + x] = 64 + x + frame_index * 5;
		}
	}
}

static void overlay_yuv_images(
	/* in & out image*/
	uint8_t *data1[AV_NUM_DATA_POINTERS], int linesize1[AV_NUM_DATA_POINTERS],
	int width1, int height1,
	/* overlay position begining at left-right corner */
	int x, int y,
	/* in image */
	uint8_t *data2[AV_NUM_DATA_POINTERS], int linesize2[AV_NUM_DATA_POINTERS],
	int width2, int height2)
{
	assert(width1 >= width2 + x);
	assert(height1 >= height2 + y);

	int x2, y2;
	/* overlay Y */
	for (y2 = 0; y2 < height2; ++y2)
		for (x2 = 0; x2 < width2; ++x2) {
			data1[0][(y + y2) * linesize1[0] + (x + x2)] =
				data2[0][y2 * linesize2[0] + x2];
		}
	/* overlay Cb and Cr */
	for (y2 = 0; y2 < height2 / 2; y2++) {
		for (x2 = 0; x2 < width2 / 2; x2++) {
			data1[1][(y/2 + y2) * linesize1[1] + (x/2 + x2)] =
				data2[1][y2 * linesize2[1] + x2];
			data1[2][(y/2 + y2) * linesize1[2] + (x/2 + x2)] =
				data2[2][y2 * linesize2[2] + x2];
		}
	}
}

static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
	FILE *outfile)
{
	int ret;

	/* send the frame to the encoder */
	if (frame)
		printf("Send frame %3" PRId64 "\n", frame->pts);

	ret = avcodec_send_frame(enc_ctx, frame);
	if (ret < 0) {
		fprintf(stderr, "Error sending a frame for encoding\n");
		exit(1);
	}

	while (ret >= 0) {
		ret = avcodec_receive_packet(enc_ctx, pkt);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			fprintf(stderr, "Error during encoding\n");
			exit(1);
		}

		printf("Write packet %3" PRId64 " (size=%5d)\n", pkt->pts, pkt->size);
		fwrite(pkt->data, 1, pkt->size, outfile);
		av_packet_unref(pkt);
	}
}

int main(int argc, char **argv)
{
	if (argc != 2) {
		fprintf(stderr, "Usage: %s <output file>\n", argv[0]);
		exit(0);
	}

	int ret;

	avcodec_register_all();
	const char* codec_name = "libx264";
	const AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
	if (!codec) {
		fprintf(stderr, "Codec '%s' not found\n", codec_name);
		exit(1);
	}
	AVCodecContext *ctx = NULL;
	ctx = avcodec_alloc_context3(codec);
	if (!ctx) {
		fprintf(stderr, "Could not allocate video codec context\n");
		exit(1);
	}
	AVPacket* pkt = av_packet_alloc();
	if (!pkt)
		exit(1);

	int w = 320;
	int h = 240;

	int ow = w / 4;
	int oh = h / 4;
	/* put sample parameters */
	ctx->bit_rate = 400000;
	/* resolution must be a multiple of two */
	ctx->width = w;
	ctx->height = h;
	/* frames per second */
	ctx->time_base = (AVRational) { 1, 25 };
	ctx->framerate = (AVRational) { 25, 1 };

	/* emit one intra frame every ten frames
	* check frame pict_type before passing frame
	* to encoder, if frame->pict_type is AV_PICTURE_TYPE_I
	* then gop_size is ignored and the output of encoder
	* will always be I frame irrespective to gop_size
	*/
	ctx->gop_size = 10;
	ctx->max_b_frames = 1;
	ctx->pix_fmt = AV_PIX_FMT_YUV420P;

	if (codec->id == AV_CODEC_ID_H264)
		av_opt_set(ctx->priv_data, "preset", "slow", 0);

	/* open it */
	ret = avcodec_open2(ctx, codec, NULL);
	if (ret < 0) {
		fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));
		exit(1);
	}

	const char* filename = argv[1];
	FILE* dst_file = fopen(filename, "wb");
	if (!dst_file) {
		fprintf(stderr, "Could not open destination file %s\n", filename);
		exit(1);
	}

	int i;
	enum AVPixelFormat pix_fmt = AV_PIX_FMT_YUV420P;

	AVFrame* frame_base = av_frame_alloc();
	if (!frame_base) {
		fprintf(stderr, "Could not allocate video frame_base\n");
		exit(1);
	}
	frame_base->format = pix_fmt;
	frame_base->width = w;
	frame_base->height = h;
	ret = av_frame_get_buffer(frame_base, 64);
	if (ret < 0) {
		fprintf(stderr, "Could not allocate the video frame data\n");
		exit(1);
	}
	ret = av_frame_make_writable(frame_base);
	if (ret < 0)
		exit(1);
	AVFrame* frame_overlay = av_frame_alloc();
	if (!frame_overlay) {
		fprintf(stderr, "Could not allocate video frame_overlay\n");
		exit(1);
	}
	frame_overlay->format = pix_fmt;
	frame_overlay->width = w / 2;
	frame_overlay->height = h / 2;
	ret = av_frame_get_buffer(frame_overlay, 64);
	if (ret < 0) {
		fprintf(stderr, "Could not allocate the video frame data\n");
		exit(1);
	}
	ret = av_frame_make_writable(frame_overlay);
	if (ret < 0)
		exit(1);
	for (i = 0; i < 250; i++) {
		fill_yuv_image(frame_base->data, frame_base->linesize, w, h, i);
		fill_yuv_image(frame_overlay->data, frame_overlay->linesize, ow, oh, i);

		int x = 10;
		int y = 10;
		x = i * 3 + 10;
		x %= (w - ow - 10);
		overlay_yuv_images(frame_base->data, frame_base->linesize, w, h,
			x, y,
			frame_overlay->data, frame_overlay->linesize, ow, oh);
		
		x = i * 5 + 10;
		x %= (w - ow - 10);
		y = y + oh + 10;
		overlay_yuv_images(frame_base->data, frame_base->linesize, w, h,
			x, y,
			frame_overlay->data, frame_overlay->linesize, ow, oh);
		
		x = i * 7 + 10;
		x %= (w - ow - 10);
		y = y + oh + 10;
		overlay_yuv_images(frame_base->data, frame_base->linesize, w, h,
			x, y,
			frame_overlay->data, frame_overlay->linesize, ow, oh);

		frame_base->pts = i + 1;
		encode(ctx, frame_base, pkt, dst_file);
	}
	encode(ctx, NULL, pkt, dst_file);

	fclose(dst_file);
	av_frame_free(&frame_base);
	av_frame_free(&frame_overlay);
	av_packet_free(&pkt);
	avcodec_free_context(&ctx);

	fprintf(stderr, "Scaling succeeded. Play the output file with the command:\n"
		"ffplay %s\n",
		filename);
}